library(dplyr)
library(ggplot2)

1. Reading the dataset

library(readr)
dataset <- read_csv("new_dataset.csv")
Rows: 568 Columns: 648── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr   (1): name
dbl (647): psd_1, psd_2, psd_3, psd_4, psd_5, psd_6, psd_7, psd_8, psd_9, psd_10, psd_11, psd_12, psd_13, psd_14, psd_15, psd_16, psd_17, psd_18, psd_19, psd_20, psd_21, psd_22, psd_23,...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

1.1 remove predictor with zero variance

library(caret)
Loading required package: lattice
nzv <- nearZeroVar(dataset, saveMetrics = FALSE)
dataset <- dataset[, -nzv]

2. Splitting the dataset

We will split the dataset into a training set (70%) and a testing set (30%).

set.seed(123) # for reproducibility
sample_index <- sample(1:nrow(dataset), 0.7*nrow(dataset))
train_data <- dataset[sample_index, ]
test_data <- dataset[-sample_index, ]

3. Creating a regression model

Using the glmnet package, we’ll predict the tmg feature

library(glmnet)
Loading required package: Matrix
Loaded glmnet 4.1-8
y <- train_data$tmg
x <- train_data %>% select(-name,-tmg) %>% as.matrix()

Normalize the data

Standardization (Z-score normalization): This method transforms each feature to have a mean of 0 and a standard deviation of 1. It’s particularly useful when your features have different units or very different scales.

library(caret)
scaled_x <- preProcess(x, method = c("center", "scale"))
x <- predict(scaled_x, x)

In the context of Elastic Net regression, the alpha parameter is a crucial component that balances the mix between Lasso (L1) and Ridge (L2) regularization methods. Elastic Net is a regularization technique that combines both L1 and L2 penalties, which are used to prevent overfitting by adding a penalty to the model’s loss function.

Here’s a breakdown of the alpha parameter:

  1. Range: alpha can take on any value between 0 and 1 (inclusive).

    • alpha = 1: The penalty is entirely Lasso (L1 regularization).
    • alpha = 0: The penalty is entirely Ridge (L2 regularization).
    • alpha between 0 and 1: A combination of Lasso and Ridge.
  2. Effect of L1 (Lasso) Regularization: L1 regularization adds a penalty equal to the absolute value of the magnitude of coefficients. This can lead to some coefficients being exactly zero, which is useful for feature selection if you have a large number of features.

  3. Effect of L2 (Ridge) Regularization: L2 regularization adds a penalty equal to the square of the magnitude of coefficients. This tends to shrink the coefficients but does not set them to zero, which is useful when you have correlated features.

  4. Choosing alpha:

    • If you have a lot of features that you suspect are not all useful, a value closer to 1 (more Lasso) might be more appropriate as it will perform feature selection.
    • If all your features are believed to be important, or you have a small number of features, a value closer to 0 (more Ridge) might work better.
    • Often, the best way to choose an alpha value is through cross-validation, trying different values and selecting the one that minimizes prediction error.
  5. Interaction with lambda: The lambda parameter in Elastic Net controls the overall strength of the penalty. So, the effect of alpha is in conjunction with lambda. A grid search over both alpha and lambda is a common practice to find the best combination that minimizes cross-validation error.

In summary, the alpha parameter in Elastic Net allows you to balance the type of regularization applied to your model, providing the flexibility to choose between Lasso, Ridge, or a mix of both based on your data and the specific requirements of your problem.

# Elastic Net model
set.seed(123)
cv_model <- cv.glmnet(x, y, alpha = 1) # alpha=0.5 indicates Elastic Net
best_lambda <- cv_model$lambda.min
model_en <- glmnet(x, y, alpha = 1, lambda = best_lambda)
print(model_en)

Call:  glmnet(x = x, y = y, alpha = 1, lambda = best_lambda) 

  Df  %Dev   Lambda
1 50 86.09 0.000727

foldid <- sample(1:10, size = length(y), replace = TRUE)
cv1  <- cv.glmnet(x, y, foldid = foldid, alpha = 1)
cv.5 <- cv.glmnet(x, y, foldid = foldid, alpha = 0.5)
cv0  <- cv.glmnet(x, y, foldid = foldid, alpha = 0)

par(mfrow = c(2,2))
Warning: unable to load shared object '/usr/local/lib/R/modules//R_X11.so':
  libXt.so.6: cannot open shared object file: No such file or directory
plot(cv1);
title("alpha=1")
plot(cv.5, );
title("alpha=0.5")
plot(cv0)
title("alpha=0")
plot(log(cv1$lambda)   , cv1$cvm , pch = 19, col = "red", xlab = "Log(λ)", ylab = cv1$name)
points(log(cv.5$lambda), cv.5$cvm, pch = 19, col = "grey")
points(log(cv0$lambda) , cv0$cvm , pch = 19, col = "blue")
legend("topleft", legend = c("alpha= 1", "alpha= .5", "alpha 0"), pch = 19, col = c("red","grey","blue"))

foldid <- sample(1:10, size = length(y), replace = TRUE)
cv1.00 <- cv.glmnet(x, y, foldid = foldid, alpha = 1.00)
cv0.75 <- cv.glmnet(x, y, foldid = foldid, alpha = 0.75)
cv0.50 <- cv.glmnet(x, y, foldid = foldid, alpha = 0.50)
cv0.25 <- cv.glmnet(x, y, foldid = foldid, alpha = 0.25)
cv0.00 <- cv.glmnet(x, y, foldid = foldid, alpha = 0.00)

par(mfrow = c(2,3))
plot(cv1.00); title("alpha=1.00")
plot(cv0.75); title("alpha=0.75")
plot(cv0.50); title("alpha=0.50")
plot(cv0.25); title("alpha=0.25")
plot(cv0.00); title("alpha=0.00")
plot(log(cv1.00$lambda), cv1.00$cvm , pch = 19, col = "red", xlab = "Log(λ)", ylab = cv1$name)
points(log(cv0.75$lambda), cv0.75$cvm, pch = 19, col = "green")
points(log(cv0.50$lambda), cv0.50$cvm, pch = 19, col = "grey")
points(log(cv0.25$lambda), cv0.25$cvm, pch = 19, col = "orange")
points(log(cv0.00$lambda), cv0.00$cvm, pch = 19, col = "blue")
alphas = c("alpha=1.00", "alpha=0.75", "alpha=0.50", "alpha=0.25", "alpha=0.00")
legend("topleft", legend = alphas, pch = 19, col = c("red","green","grey","orange","blue"))

cvm=c(min(cv1.00$cvm), min(cv0.75$cvm), min(cv0.50$cvm), min(cv0.25$cvm), min(cv0.00$cvm))
loglambda=c(log(cv1.00$lambda[which.min(cv1.00$cvm)]),log(cv0.75$lambda[which.min(cv0.75$cvm)]), log(cv0.50$lambda[which.min(cv0.50$cvm)]), log(cv0.25$lambda[which.min(cv0.25$cvm)]), log(cv0.00$lambda[which.min(cv0.00$cvm)]))
data.frame(alphas=alphas, CVM=cvm, minCVMLogLambda=loglambda)
# install.packages("parallel")
library(parallel)
foldid <- sample(1:10, size = length(y), replace = TRUE)
test_alpha <- function(alpha){
  net <- cv.glmnet(x, y, foldid = foldid, alpha = alpha)
  per05 <- apply(cbind(a=net$cvm, b=log(net$lambda)), 2, quantile, probs = 0.05)
  per10 <- apply(cbind(a=net$cvm, b=log(net$lambda)), 2, quantile, probs = 0.10)
  per15 <- apply(cbind(a=net$cvm, b=log(net$lambda)), 2, quantile, probs = 0.15)
  per20 <- apply(cbind(a=net$cvm, b=log(net$lambda)), 2, quantile, probs = 0.20)
  
  return(data.frame(
    alpha=alpha,
    minCVM=min(net$cvm),
    bestLambda=log(net$lambda[which.min(net$cvm)]),
    per05CVM=per05[1],
    per10CVM=per10[1],
    per15CVM=per15[1],
    per20CVM=per20[1]
  ))
}
test_alphas <- seq(from = 0, to = 1, length.out = 1000)
test_results <- bind_rows(mclapply(test_alphas, test_alpha, mc.cores=32))
# test_results <- bind_rows(lapply(test_alphas, test_alpha))
print(test_results)
test_results
# par(mfrow = c(2, 1))
plot(x=test_results$alpha, y=test_results$bestLambda, ylab="bestLambda")

plot(x=test_results$alpha, y=test_results$minCVM, ylab="00 percentile")

# plot(x=test_results$alpha, y=test_results$per05CVM, ylab="05 percentile")
# plot(x=test_results$alpha, y=test_results$per10CVM, ylab="10 percentile")
# plot(x=test_results$alpha, y=test_results$per15CVM, ylab="15 percentile")
# plot(x=test_results$alpha, y=test_results$per20CVM, ylab="20 percentile")

4. Calculate importance using elasticnet coeficients

# The variable importance is inferred from the coefficients
calculate_top_importance <- function(model, top_n){
  c <- coef(model)
  predictor <- c  %>% rownames()
  importance <- c %>% as.matrix()
  importance <- data.frame(predictor,importance)
  rownames(importance) <- NULL
  names(importance)[2] <- "importance"
  importance<-importance[-1,] # Exclude intercept
  importance <- importance[order(-importance$importance), ]
  importance <- head(importance, top_n)
  return(importance)
}
alpha0 <- cv.glmnet(x, y, foldid = foldid, alpha = 0.00)
alpha1 <- cv.glmnet(x, y, foldid = foldid, alpha = 1.00)
top0 <- calculate_top_importance(alpha0, 20)
top1 <- calculate_top_importance(alpha1, 20)
library(dplyr)
library(ggplot2)
plot_importance <- function(importance){
  plot_perm <-importance  %>%  mutate(predictor = factor(predictor, levels = rev(unique(predictor)))) %>%
    ggplot()+
    geom_col(aes(y=predictor,x=importance),fill='darkblue', color='gray')+
    ggtitle("Top 20 predictor importance using glmnet")+
    theme_minimal()
  return(plot_perm)
}
plot_importance(top0)

plot_importance(top1)

5. Evaluate results on test dataset

get_rmse <- function(data){
  x_test <- data %>% select(-name,-tmg) %>% as.matrix()
  y_test <- data$tmg

  x_test <-predict(scaled_x, x_test)

  predictions <- predict(model_en, s = best_lambda, newx = x_test)
  RMSE <- sqrt(mean((predictions - y_test)^2))
  return(RMSE)
}
print(get_rmse(test_data))
[1] 0.03531884
print(get_rmse(dataset))
[1] 0.03187589

6. Plot: Predicted vs Reference values


library(readr)
library(caret)
Loading required package: ggplot2
Loading required package: lattice
library(glmnet)
Loading required package: Matrix
Loaded glmnet 4.1-8
library(caret)
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
dataset <- read_csv("new_dataset.csv")
Rows: 568 Columns: 648── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr   (1): name
dbl (647): psd_1, psd_2, psd_3, psd_4, psd_5, psd_6, psd_7, psd_8, psd_9, psd_10, psd_11, psd_12, psd_13, psd_14, psd_15, psd_16, psd_17, psd_18, psd_19, psd_20, psd_21, psd_22, psd_23,...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
nzv <- nearZeroVar(dataset, saveMetrics = FALSE)
dataset <- dataset[, -nzv]
set.seed(123) # for reproducibility
sample_index <- sample(1:nrow(dataset), 0.7*nrow(dataset))
train_data <- dataset[sample_index, ]
test_data <- dataset[-sample_index, ]
y_train <- train_data$tmg
x_train <- train_data %>% select(-name,-tmg) %>% as.matrix()
scaled_x <- preProcess(x_train, method = c("center", "scale"))
x_train <- predict(scaled_x, x_train)
set.seed(123)
alpha = 1
foldid <- sample(1:10, size = length(y_train), replace = TRUE)
foldid
  [1]  3  3 10  2  6  5  4  6  9 10  5  3  9  9  9  3  8 10  7 10  9  3  4  1  7  5 10  7  9  9 10  7  5  7  5  6  9  2  5  8  2  1  9  9  6  5  9 10  4  6  8  6  6  7  1  6  2  1  2  4  5
 [62]  6  3  9  4  6  9  9  7  3  8  9  3  7  3  7  6 10  5  5  8  3 10  2 10  2 10  6  4  1  6  3  8  3  8  1  7  7  7 10  6  7 10  5  6  8  5  7  4  3  9  7  6 10  9  7  2  3  8  4  7  4
[123]  1  8  4  9  8  6  4  8  3  4  4  6  1 10  4  9  7  8  5  2  6  9  8 10  4  5  7  1  8  8 10  9  8  2  5  9  7  7 10 10  8  6  7 10  1  9  3 10  5  6  9  4 10  7  9  7 10  4  8  9  9
[184]  9  5  7  6  1 10 10  1 10  1 10  5  7  5 10  9  4  6  2  1  5  9  4  3  9  1  2  4 10  1  5  5  9  8  7  9  5  2 10  6  7  9  1  5  5  8  5  7  4  2  1  1  2  1  2  8  1  3  2  2  5
[245]  9 10  6 10 10  6  3  4  3  7  3  2  7  3  9  7  9  4  2  6 10  9  7  5  1  3  2  5  1  3  9  2  6  3  3  9  2  1  6 10  8 10 10  4  6  4  4  9  8  7 10  1  2  8  3 10  7  1  2  3  3
[306]  6 10  5  4  2  1  1  5  2  2  3  5  6 10  3  3  1  8  1  4  8 10 10  8  7  2  1  4  9  5  8  2  5  2  6 10 10  9  7  6  3  8  7  4  8  3  8  6  4  7 10  8  4 10  9  6  3  4  9  9  4
[367]  4  6  5  1  8  7  5  6  3  8 10  5  7  4  9  3 10  7  2  9  6  4  6  2  2  8  8  5  9  7  3
cv_model <- cv.glmnet(x_train, y_train, foldid = foldid, alpha = alpha)
best_lambda <- cv_model$lambda.min
model_en <- glmnet(x_train, y_train, alpha = alpha, foldid = foldid, lambda = best_lambda)

do_plot <- function(data, type) {
  x_eval <- data %>% select(-name,-tmg) %>% as.matrix()
  y_eval <- data$tmg
  x_eval <- predict(scaled_x, x_eval)
  
  predictions <- predict(model_en, s = best_lambda, newx = x_eval)
  RMSE <- sqrt(mean((predictions - y_eval)^2))
  print(paste0(RMSE, type))
  results <- data.frame(Reference = y_eval, Predicted = as.vector(predictions))
  return(ggplot(results, aes(x = Reference, y = Predicted)) +
    geom_point(color='blue') +
    geom_abline(intercept = 0, slope = 1, color='red') +
    ggtitle(paste(type, "glmnet; RMSE:", RMSE)) +
    xlab("Reference Values") +
    ylab("Predicted Values") +
    theme_bw())
}
do_plot(test_data, "Test")
[1] "0.0353347640606086Test"

do_plot(train_data, "Train")
[1] "0.0309269148825548Train"

do_plot(dataset, "Total")
[1] "0.032317240545742Total"

best_lambda
[1] 0.0009610472
# install.packages("gridExtra")
library(gridExtra)
library(parallel)

plot_column <- function(column, importance){
  results <- data.frame(x = dataset$tmg, y = dataset[[column]])
  plt <- ggplot(results, aes(x = x, y = y)) +
    geom_point(color='blue') +
     xlab("") +
    ylab(column) +
    theme_bw()
  return(plt)  
}

plot_important <- function(important, title, n_to_plot=6, columns=2) grid.arrange(top=title, grobs = mclapply(important$predictor[1:n_to_plot], plot_column, importance=important$importance[1:n_to_plot]), ncol = columns)

# plot_list1 <- lapply(top1$predictor[1:6], plot_column)
# grid.arrange(top="Alpha=1", grobs = plot_list1, ncol = 2)

# plot_list0 <- lapply(top0$predictor[1:6], plot_column)
# grid.arrange(top="Alpha=0", grobs = plot_list0, ncol = 2)
top0
png("alpha0.png", width = 1000, height = 1200)
plot_important(top0, n_to_plot=20, columns=4, "Alpha=0")
dev.off()
null device 
          1 
png("alpha1.png", width = 1000, height = 1200)
plot_important(top1, n_to_plot=20, columns=4, "Alpha=1")
dev.off()
null device 
          1 
full_dataset <- read_csv("new_dataset.csv")
Rows: 568 Columns: 648── Column specification ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr   (1): name
dbl (647): psd_1, psd_2, psd_3, psd_4, psd_5, psd_6, psd_7, psd_8, psd_9, psd_10, psd_11, psd_12, psd_13, psd_14, psd_15, psd_16, psd_17, psd_18...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
full_dataset
selected_columns <- full_dataset[, grep("(^psd_)", names(full_dataset))]
plot(t(selected_columns[1,]))

LS0tCnRpdGxlOiAiUmVncmVzc2lvbiBNb2RlbCB1c2luZyBFbGFzdGljIE5ldCBmb3IgRmVOaSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMjIDEuIFJlYWRpbmcgdGhlIGRhdGFzZXQKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpkYXRhc2V0IDwtIHJlYWRfY3N2KCJuZXdfZGF0YXNldC5jc3YiKQpgYGAKCiMjIDEuMSByZW1vdmUgcHJlZGljdG9yIHdpdGggemVybyB2YXJpYW5jZQoKYGBge3J9CmxpYnJhcnkoY2FyZXQpCm56diA8LSBuZWFyWmVyb1ZhcihkYXRhc2V0LCBzYXZlTWV0cmljcyA9IEZBTFNFKQpkYXRhc2V0IDwtIGRhdGFzZXRbLCAtbnp2XQpgYGAKCgojIyAyLiBTcGxpdHRpbmcgdGhlIGRhdGFzZXQKV2Ugd2lsbCBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIGEgdHJhaW5pbmcgc2V0ICg3MCUpIGFuZCBhIHRlc3Rpbmcgc2V0ICgzMCUpLgoKYGBge3J9CnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnNhbXBsZV9pbmRleCA8LSBzYW1wbGUoMTpucm93KGRhdGFzZXQpLCAwLjcqbnJvdyhkYXRhc2V0KSkKdHJhaW5fZGF0YSA8LSBkYXRhc2V0W3NhbXBsZV9pbmRleCwgXQp0ZXN0X2RhdGEgPC0gZGF0YXNldFstc2FtcGxlX2luZGV4LCBdCmBgYAoKIyMgMy4gQ3JlYXRpbmcgYSByZWdyZXNzaW9uIG1vZGVsClVzaW5nIHRoZSBnbG1uZXQgcGFja2FnZSwgd2UnbGwgcHJlZGljdCB0aGUgYHRtZ2AgZmVhdHVyZQoKYGBge3J9CmxpYnJhcnkoZ2xtbmV0KQp5IDwtIHRyYWluX2RhdGEkdG1nCnggPC0gdHJhaW5fZGF0YSAlPiUgc2VsZWN0KC1uYW1lLC10bWcpICU+JSBhcy5tYXRyaXgoKQpgYGAKCiMjIyBOb3JtYWxpemUgdGhlIGRhdGEKClN0YW5kYXJkaXphdGlvbiAoWi1zY29yZSBub3JtYWxpemF0aW9uKTogVGhpcyBtZXRob2QgdHJhbnNmb3JtcyBlYWNoIGZlYXR1cmUgdG8gaGF2ZSBhIG1lYW4gb2YgMCBhbmQgYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgMS4gSXQncyBwYXJ0aWN1bGFybHkgdXNlZnVsIHdoZW4geW91ciBmZWF0dXJlcyBoYXZlIGRpZmZlcmVudCB1bml0cyBvciB2ZXJ5IGRpZmZlcmVudCBzY2FsZXMuCmBgYHtyfQpsaWJyYXJ5KGNhcmV0KQpzY2FsZWRfeCA8LSBwcmVQcm9jZXNzKHgsIG1ldGhvZCA9IGMoImNlbnRlciIsICJzY2FsZSIpKQp4IDwtIHByZWRpY3Qoc2NhbGVkX3gsIHgpCmBgYAoKSW4gdGhlIGNvbnRleHQgb2YgRWxhc3RpYyBOZXQgcmVncmVzc2lvbiwgdGhlIGBhbHBoYWAgcGFyYW1ldGVyIGlzIGEgY3J1Y2lhbCBjb21wb25lbnQgdGhhdCBiYWxhbmNlcyB0aGUgbWl4IGJldHdlZW4gTGFzc28gKEwxKSBhbmQgUmlkZ2UgKEwyKSByZWd1bGFyaXphdGlvbiBtZXRob2RzLiBFbGFzdGljIE5ldCBpcyBhIHJlZ3VsYXJpemF0aW9uIHRlY2huaXF1ZSB0aGF0IGNvbWJpbmVzIGJvdGggTDEgYW5kIEwyIHBlbmFsdGllcywgd2hpY2ggYXJlIHVzZWQgdG8gcHJldmVudCBvdmVyZml0dGluZyBieSBhZGRpbmcgYSBwZW5hbHR5IHRvIHRoZSBtb2RlbCdzIGxvc3MgZnVuY3Rpb24uCgpIZXJlJ3MgYSBicmVha2Rvd24gb2YgdGhlIGBhbHBoYWAgcGFyYW1ldGVyOgoKMS4gKipSYW5nZSoqOiBgYWxwaGFgIGNhbiB0YWtlIG9uIGFueSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDEgKGluY2x1c2l2ZSkuIAogICAtIGBhbHBoYSA9IDFgOiBUaGUgcGVuYWx0eSBpcyBlbnRpcmVseSBMYXNzbyAoTDEgcmVndWxhcml6YXRpb24pLgogICAtIGBhbHBoYSA9IDBgOiBUaGUgcGVuYWx0eSBpcyBlbnRpcmVseSBSaWRnZSAoTDIgcmVndWxhcml6YXRpb24pLgogICAtIGBhbHBoYWAgYmV0d2VlbiAwIGFuZCAxOiBBIGNvbWJpbmF0aW9uIG9mIExhc3NvIGFuZCBSaWRnZS4KCjIuICoqRWZmZWN0IG9mIEwxIChMYXNzbykgUmVndWxhcml6YXRpb24qKjogTDEgcmVndWxhcml6YXRpb24gYWRkcyBhIHBlbmFsdHkgZXF1YWwgdG8gdGhlIGFic29sdXRlIHZhbHVlIG9mIHRoZSBtYWduaXR1ZGUgb2YgY29lZmZpY2llbnRzLiBUaGlzIGNhbiBsZWFkIHRvIHNvbWUgY29lZmZpY2llbnRzIGJlaW5nIGV4YWN0bHkgemVybywgd2hpY2ggaXMgdXNlZnVsIGZvciBmZWF0dXJlIHNlbGVjdGlvbiBpZiB5b3UgaGF2ZSBhIGxhcmdlIG51bWJlciBvZiBmZWF0dXJlcy4KCjMuICoqRWZmZWN0IG9mIEwyIChSaWRnZSkgUmVndWxhcml6YXRpb24qKjogTDIgcmVndWxhcml6YXRpb24gYWRkcyBhIHBlbmFsdHkgZXF1YWwgdG8gdGhlIHNxdWFyZSBvZiB0aGUgbWFnbml0dWRlIG9mIGNvZWZmaWNpZW50cy4gVGhpcyB0ZW5kcyB0byBzaHJpbmsgdGhlIGNvZWZmaWNpZW50cyBidXQgZG9lcyBub3Qgc2V0IHRoZW0gdG8gemVybywgd2hpY2ggaXMgdXNlZnVsIHdoZW4geW91IGhhdmUgY29ycmVsYXRlZCBmZWF0dXJlcy4KCjQuICoqQ2hvb3NpbmcgYGFscGhhYCoqOiAKICAgLSBJZiB5b3UgaGF2ZSBhIGxvdCBvZiBmZWF0dXJlcyB0aGF0IHlvdSBzdXNwZWN0IGFyZSBub3QgYWxsIHVzZWZ1bCwgYSB2YWx1ZSBjbG9zZXIgdG8gMSAobW9yZSBMYXNzbykgbWlnaHQgYmUgbW9yZSBhcHByb3ByaWF0ZSBhcyBpdCB3aWxsIHBlcmZvcm0gZmVhdHVyZSBzZWxlY3Rpb24uCiAgIC0gSWYgYWxsIHlvdXIgZmVhdHVyZXMgYXJlIGJlbGlldmVkIHRvIGJlIGltcG9ydGFudCwgb3IgeW91IGhhdmUgYSBzbWFsbCBudW1iZXIgb2YgZmVhdHVyZXMsIGEgdmFsdWUgY2xvc2VyIHRvIDAgKG1vcmUgUmlkZ2UpIG1pZ2h0IHdvcmsgYmV0dGVyLgogICAtIE9mdGVuLCB0aGUgYmVzdCB3YXkgdG8gY2hvb3NlIGFuIGBhbHBoYWAgdmFsdWUgaXMgdGhyb3VnaCBjcm9zcy12YWxpZGF0aW9uLCB0cnlpbmcgZGlmZmVyZW50IHZhbHVlcyBhbmQgc2VsZWN0aW5nIHRoZSBvbmUgdGhhdCBtaW5pbWl6ZXMgcHJlZGljdGlvbiBlcnJvci4KCjUuICoqSW50ZXJhY3Rpb24gd2l0aCBgbGFtYmRhYCoqOiBUaGUgYGxhbWJkYWAgcGFyYW1ldGVyIGluIEVsYXN0aWMgTmV0IGNvbnRyb2xzIHRoZSBvdmVyYWxsIHN0cmVuZ3RoIG9mIHRoZSBwZW5hbHR5LiBTbywgdGhlIGVmZmVjdCBvZiBgYWxwaGFgIGlzIGluIGNvbmp1bmN0aW9uIHdpdGggYGxhbWJkYWAuIEEgZ3JpZCBzZWFyY2ggb3ZlciBib3RoIGBhbHBoYWAgYW5kIGBsYW1iZGFgIGlzIGEgY29tbW9uIHByYWN0aWNlIHRvIGZpbmQgdGhlIGJlc3QgY29tYmluYXRpb24gdGhhdCBtaW5pbWl6ZXMgY3Jvc3MtdmFsaWRhdGlvbiBlcnJvci4KCkluIHN1bW1hcnksIHRoZSBgYWxwaGFgIHBhcmFtZXRlciBpbiBFbGFzdGljIE5ldCBhbGxvd3MgeW91IHRvIGJhbGFuY2UgdGhlIHR5cGUgb2YgcmVndWxhcml6YXRpb24gYXBwbGllZCB0byB5b3VyIG1vZGVsLCBwcm92aWRpbmcgdGhlIGZsZXhpYmlsaXR5IHRvIGNob29zZSBiZXR3ZWVuIExhc3NvLCBSaWRnZSwgb3IgYSBtaXggb2YgYm90aCBiYXNlZCBvbiB5b3VyIGRhdGEgYW5kIHRoZSBzcGVjaWZpYyByZXF1aXJlbWVudHMgb2YgeW91ciBwcm9ibGVtLgoKYGBge3J9CiMgRWxhc3RpYyBOZXQgbW9kZWwKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbCA8LSBjdi5nbG1uZXQoeCwgeSwgYWxwaGEgPSAxKSAjIGFscGhhPTAuNSBpbmRpY2F0ZXMgRWxhc3RpYyBOZXQKYmVzdF9sYW1iZGEgPC0gY3ZfbW9kZWwkbGFtYmRhLm1pbgptb2RlbF9lbiA8LSBnbG1uZXQoeCwgeSwgYWxwaGEgPSAxLCBsYW1iZGEgPSBiZXN0X2xhbWJkYSkKcHJpbnQobW9kZWxfZW4pCmBgYAoKYGBge3J9Cgpmb2xkaWQgPC0gc2FtcGxlKDE6MTAsIHNpemUgPSBsZW5ndGgoeSksIHJlcGxhY2UgPSBUUlVFKQpjdjEgIDwtIGN2LmdsbW5ldCh4LCB5LCBmb2xkaWQgPSBmb2xkaWQsIGFscGhhID0gMSkKY3YuNSA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IDAuNSkKY3YwICA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IDApCgpwYXIobWZyb3cgPSBjKDIsMikpCnBsb3QoY3YxKTsKdGl0bGUoImFscGhhPTEiKQpwbG90KGN2LjUsICk7CnRpdGxlKCJhbHBoYT0wLjUiKQpwbG90KGN2MCkKdGl0bGUoImFscGhhPTAiKQpwbG90KGxvZyhjdjEkbGFtYmRhKSAgICwgY3YxJGN2bSAsIHBjaCA9IDE5LCBjb2wgPSAicmVkIiwgeGxhYiA9ICJMb2cozrspIiwgeWxhYiA9IGN2MSRuYW1lKQpwb2ludHMobG9nKGN2LjUkbGFtYmRhKSwgY3YuNSRjdm0sIHBjaCA9IDE5LCBjb2wgPSAiZ3JleSIpCnBvaW50cyhsb2coY3YwJGxhbWJkYSkgLCBjdjAkY3ZtICwgcGNoID0gMTksIGNvbCA9ICJibHVlIikKbGVnZW5kKCJ0b3BsZWZ0IiwgbGVnZW5kID0gYygiYWxwaGE9IDEiLCAiYWxwaGE9IC41IiwgImFscGhhIDAiKSwgcGNoID0gMTksIGNvbCA9IGMoInJlZCIsImdyZXkiLCJibHVlIikpCgpgYGAKYGBge3J9CmZvbGRpZCA8LSBzYW1wbGUoMToxMCwgc2l6ZSA9IGxlbmd0aCh5KSwgcmVwbGFjZSA9IFRSVUUpCmN2MS4wMCA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IDEuMDApCmN2MC43NSA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IDAuNzUpCmN2MC41MCA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IDAuNTApCmN2MC4yNSA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IDAuMjUpCmN2MC4wMCA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IDAuMDApCgpwYXIobWZyb3cgPSBjKDIsMykpCnBsb3QoY3YxLjAwKTsgdGl0bGUoImFscGhhPTEuMDAiKQpwbG90KGN2MC43NSk7IHRpdGxlKCJhbHBoYT0wLjc1IikKcGxvdChjdjAuNTApOyB0aXRsZSgiYWxwaGE9MC41MCIpCnBsb3QoY3YwLjI1KTsgdGl0bGUoImFscGhhPTAuMjUiKQpwbG90KGN2MC4wMCk7IHRpdGxlKCJhbHBoYT0wLjAwIikKcGxvdChsb2coY3YxLjAwJGxhbWJkYSksIGN2MS4wMCRjdm0gLCBwY2ggPSAxOSwgY29sID0gInJlZCIsIHhsYWIgPSAiTG9nKM67KSIsIHlsYWIgPSBjdjEkbmFtZSkKcG9pbnRzKGxvZyhjdjAuNzUkbGFtYmRhKSwgY3YwLjc1JGN2bSwgcGNoID0gMTksIGNvbCA9ICJncmVlbiIpCnBvaW50cyhsb2coY3YwLjUwJGxhbWJkYSksIGN2MC41MCRjdm0sIHBjaCA9IDE5LCBjb2wgPSAiZ3JleSIpCnBvaW50cyhsb2coY3YwLjI1JGxhbWJkYSksIGN2MC4yNSRjdm0sIHBjaCA9IDE5LCBjb2wgPSAib3JhbmdlIikKcG9pbnRzKGxvZyhjdjAuMDAkbGFtYmRhKSwgY3YwLjAwJGN2bSwgcGNoID0gMTksIGNvbCA9ICJibHVlIikKYWxwaGFzID0gYygiYWxwaGE9MS4wMCIsICJhbHBoYT0wLjc1IiwgImFscGhhPTAuNTAiLCAiYWxwaGE9MC4yNSIsICJhbHBoYT0wLjAwIikKbGVnZW5kKCJ0b3BsZWZ0IiwgbGVnZW5kID0gYWxwaGFzLCBwY2ggPSAxOSwgY29sID0gYygicmVkIiwiZ3JlZW4iLCJncmV5Iiwib3JhbmdlIiwiYmx1ZSIpKQpjdm09YyhtaW4oY3YxLjAwJGN2bSksIG1pbihjdjAuNzUkY3ZtKSwgbWluKGN2MC41MCRjdm0pLCBtaW4oY3YwLjI1JGN2bSksIG1pbihjdjAuMDAkY3ZtKSkKbG9nbGFtYmRhPWMobG9nKGN2MS4wMCRsYW1iZGFbd2hpY2gubWluKGN2MS4wMCRjdm0pXSksbG9nKGN2MC43NSRsYW1iZGFbd2hpY2gubWluKGN2MC43NSRjdm0pXSksIGxvZyhjdjAuNTAkbGFtYmRhW3doaWNoLm1pbihjdjAuNTAkY3ZtKV0pLCBsb2coY3YwLjI1JGxhbWJkYVt3aGljaC5taW4oY3YwLjI1JGN2bSldKSwgbG9nKGN2MC4wMCRsYW1iZGFbd2hpY2gubWluKGN2MC4wMCRjdm0pXSkpCmRhdGEuZnJhbWUoYWxwaGFzPWFscGhhcywgQ1ZNPWN2bSwgbWluQ1ZNTG9nTGFtYmRhPWxvZ2xhbWJkYSkKYGBgCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJwYXJhbGxlbCIpCmxpYnJhcnkocGFyYWxsZWwpCmBgYAoKYGBge3J9CmZvbGRpZCA8LSBzYW1wbGUoMToxMCwgc2l6ZSA9IGxlbmd0aCh5KSwgcmVwbGFjZSA9IFRSVUUpCnRlc3RfYWxwaGEgPC0gZnVuY3Rpb24oYWxwaGEpewogIG5ldCA8LSBjdi5nbG1uZXQoeCwgeSwgZm9sZGlkID0gZm9sZGlkLCBhbHBoYSA9IGFscGhhKQogIHBlcjA1IDwtIGFwcGx5KGNiaW5kKGE9bmV0JGN2bSwgYj1sb2cobmV0JGxhbWJkYSkpLCAyLCBxdWFudGlsZSwgcHJvYnMgPSAwLjA1KQogIHBlcjEwIDwtIGFwcGx5KGNiaW5kKGE9bmV0JGN2bSwgYj1sb2cobmV0JGxhbWJkYSkpLCAyLCBxdWFudGlsZSwgcHJvYnMgPSAwLjEwKQogIHBlcjE1IDwtIGFwcGx5KGNiaW5kKGE9bmV0JGN2bSwgYj1sb2cobmV0JGxhbWJkYSkpLCAyLCBxdWFudGlsZSwgcHJvYnMgPSAwLjE1KQogIHBlcjIwIDwtIGFwcGx5KGNiaW5kKGE9bmV0JGN2bSwgYj1sb2cobmV0JGxhbWJkYSkpLCAyLCBxdWFudGlsZSwgcHJvYnMgPSAwLjIwKQogIAogIHJldHVybihkYXRhLmZyYW1lKAogICAgYWxwaGE9YWxwaGEsCiAgICBtaW5DVk09bWluKG5ldCRjdm0pLAogICAgYmVzdExhbWJkYT1sb2cobmV0JGxhbWJkYVt3aGljaC5taW4obmV0JGN2bSldKSwKICAgIHBlcjA1Q1ZNPXBlcjA1WzFdLAogICAgcGVyMTBDVk09cGVyMTBbMV0sCiAgICBwZXIxNUNWTT1wZXIxNVsxXSwKICAgIHBlcjIwQ1ZNPXBlcjIwWzFdCiAgKSkKfQp0ZXN0X2FscGhhcyA8LSBzZXEoZnJvbSA9IDAsIHRvID0gMSwgbGVuZ3RoLm91dCA9IDEwMDApCnRlc3RfcmVzdWx0cyA8LSBiaW5kX3Jvd3MobWNsYXBwbHkodGVzdF9hbHBoYXMsIHRlc3RfYWxwaGEsIG1jLmNvcmVzPTMyKSkKIyB0ZXN0X3Jlc3VsdHMgPC0gYmluZF9yb3dzKGxhcHBseSh0ZXN0X2FscGhhcywgdGVzdF9hbHBoYSkpCmBgYAoKYGBge3J9CnByaW50KHRlc3RfcmVzdWx0cykKdGVzdF9yZXN1bHRzCiMgcGFyKG1mcm93ID0gYygyLCAxKSkKcGxvdCh4PXRlc3RfcmVzdWx0cyRhbHBoYSwgeT10ZXN0X3Jlc3VsdHMkYmVzdExhbWJkYSwgeWxhYj0iYmVzdExhbWJkYSIpCnBsb3QoeD10ZXN0X3Jlc3VsdHMkYWxwaGEsIHk9dGVzdF9yZXN1bHRzJG1pbkNWTSwgeWxhYj0iMDAgcGVyY2VudGlsZSIpCiMgcGxvdCh4PXRlc3RfcmVzdWx0cyRhbHBoYSwgeT10ZXN0X3Jlc3VsdHMkcGVyMDVDVk0sIHlsYWI9IjA1IHBlcmNlbnRpbGUiKQojIHBsb3QoeD10ZXN0X3Jlc3VsdHMkYWxwaGEsIHk9dGVzdF9yZXN1bHRzJHBlcjEwQ1ZNLCB5bGFiPSIxMCBwZXJjZW50aWxlIikKIyBwbG90KHg9dGVzdF9yZXN1bHRzJGFscGhhLCB5PXRlc3RfcmVzdWx0cyRwZXIxNUNWTSwgeWxhYj0iMTUgcGVyY2VudGlsZSIpCiMgcGxvdCh4PXRlc3RfcmVzdWx0cyRhbHBoYSwgeT10ZXN0X3Jlc3VsdHMkcGVyMjBDVk0sIHlsYWI9IjIwIHBlcmNlbnRpbGUiKQpgYGAKIyMgNC4gQ2FsY3VsYXRlIGltcG9ydGFuY2UgdXNpbmcgZWxhc3RpY25ldCBjb2VmaWNpZW50cwoKYGBge3J9CiMgVGhlIHZhcmlhYmxlIGltcG9ydGFuY2UgaXMgaW5mZXJyZWQgZnJvbSB0aGUgY29lZmZpY2llbnRzCmNhbGN1bGF0ZV90b3BfaW1wb3J0YW5jZSA8LSBmdW5jdGlvbihtb2RlbCwgdG9wX24pewogIGMgPC0gY29lZihtb2RlbCkKICBwcmVkaWN0b3IgPC0gYyAgJT4lIHJvd25hbWVzKCkKICBpbXBvcnRhbmNlIDwtIGMgJT4lIGFzLm1hdHJpeCgpCiAgaW1wb3J0YW5jZSA8LSBkYXRhLmZyYW1lKHByZWRpY3RvcixpbXBvcnRhbmNlKQogIHJvd25hbWVzKGltcG9ydGFuY2UpIDwtIE5VTEwKICBuYW1lcyhpbXBvcnRhbmNlKVsyXSA8LSAiaW1wb3J0YW5jZSIKICBpbXBvcnRhbmNlPC1pbXBvcnRhbmNlWy0xLF0gIyBFeGNsdWRlIGludGVyY2VwdAogIGltcG9ydGFuY2UgPC0gaW1wb3J0YW5jZVtvcmRlcigtaW1wb3J0YW5jZSRpbXBvcnRhbmNlKSwgXQogIGltcG9ydGFuY2UgPC0gaGVhZChpbXBvcnRhbmNlLCB0b3BfbikKICByZXR1cm4oaW1wb3J0YW5jZSkKfQphbHBoYTAgPC0gY3YuZ2xtbmV0KHgsIHksIGZvbGRpZCA9IGZvbGRpZCwgYWxwaGEgPSAwLjAwKQphbHBoYTEgPC0gY3YuZ2xtbmV0KHgsIHksIGZvbGRpZCA9IGZvbGRpZCwgYWxwaGEgPSAxLjAwKQp0b3AwIDwtIGNhbGN1bGF0ZV90b3BfaW1wb3J0YW5jZShhbHBoYTAsIDIwKQp0b3AxIDwtIGNhbGN1bGF0ZV90b3BfaW1wb3J0YW5jZShhbHBoYTEsIDIwKQpgYGAKCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpwbG90X2ltcG9ydGFuY2UgPC0gZnVuY3Rpb24oaW1wb3J0YW5jZSl7CiAgcGxvdF9wZXJtIDwtaW1wb3J0YW5jZSAgJT4lICBtdXRhdGUocHJlZGljdG9yID0gZmFjdG9yKHByZWRpY3RvciwgbGV2ZWxzID0gcmV2KHVuaXF1ZShwcmVkaWN0b3IpKSkpICU+JQogICAgZ2dwbG90KCkrCiAgICBnZW9tX2NvbChhZXMoeT1wcmVkaWN0b3IseD1pbXBvcnRhbmNlKSxmaWxsPSdkYXJrYmx1ZScsIGNvbG9yPSdncmF5JykrCiAgICBnZ3RpdGxlKCJUb3AgMjAgcHJlZGljdG9yIGltcG9ydGFuY2UgdXNpbmcgZ2xtbmV0IikrCiAgICB0aGVtZV9taW5pbWFsKCkKICByZXR1cm4ocGxvdF9wZXJtKQp9CnBsb3RfaW1wb3J0YW5jZSh0b3AwKQpwbG90X2ltcG9ydGFuY2UodG9wMSkKYGBgCiMjIDUuIEV2YWx1YXRlIHJlc3VsdHMgb24gdGVzdCBkYXRhc2V0CgpgYGB7cn0KZ2V0X3Jtc2UgPC0gZnVuY3Rpb24oZGF0YSl7CiAgeF90ZXN0IDwtIGRhdGEgJT4lIHNlbGVjdCgtbmFtZSwtdG1nKSAlPiUgYXMubWF0cml4KCkKICB5X3Rlc3QgPC0gZGF0YSR0bWcKCiAgeF90ZXN0IDwtcHJlZGljdChzY2FsZWRfeCwgeF90ZXN0KQoKICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsX2VuLCBzID0gYmVzdF9sYW1iZGEsIG5ld3ggPSB4X3Rlc3QpCiAgUk1TRSA8LSBzcXJ0KG1lYW4oKHByZWRpY3Rpb25zIC0geV90ZXN0KV4yKSkKICByZXR1cm4oUk1TRSkKfQpwcmludChnZXRfcm1zZSh0ZXN0X2RhdGEpKQpwcmludChnZXRfcm1zZShkYXRhc2V0KSkKYGBgCgojIyA2LiBQbG90OiBQcmVkaWN0ZWQgdnMgUmVmZXJlbmNlIHZhbHVlcwoKYGBge3J9CgpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGdsbW5ldCkKbGlicmFyeShjYXJldCkKbGlicmFyeShkcGx5cikKZGF0YXNldCA8LSByZWFkX2NzdigibmV3X2RhdGFzZXQuY3N2IikKbnp2IDwtIG5lYXJaZXJvVmFyKGRhdGFzZXQsIHNhdmVNZXRyaWNzID0gRkFMU0UpCmRhdGFzZXQgPC0gZGF0YXNldFssIC1uenZdCnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnNhbXBsZV9pbmRleCA8LSBzYW1wbGUoMTpucm93KGRhdGFzZXQpLCAwLjcqbnJvdyhkYXRhc2V0KSkKdHJhaW5fZGF0YSA8LSBkYXRhc2V0W3NhbXBsZV9pbmRleCwgXQp0ZXN0X2RhdGEgPC0gZGF0YXNldFstc2FtcGxlX2luZGV4LCBdCnlfdHJhaW4gPC0gdHJhaW5fZGF0YSR0bWcKeF90cmFpbiA8LSB0cmFpbl9kYXRhICU+JSBzZWxlY3QoLW5hbWUsLXRtZykgJT4lIGFzLm1hdHJpeCgpCnNjYWxlZF94IDwtIHByZVByb2Nlc3MoeF90cmFpbiwgbWV0aG9kID0gYygiY2VudGVyIiwgInNjYWxlIikpCnhfdHJhaW4gPC0gcHJlZGljdChzY2FsZWRfeCwgeF90cmFpbikKc2V0LnNlZWQoMTIzKQphbHBoYSA9IDEKZm9sZGlkIDwtIHNhbXBsZSgxOjEwLCBzaXplID0gbGVuZ3RoKHlfdHJhaW4pLCByZXBsYWNlID0gVFJVRSkKZm9sZGlkCmN2X21vZGVsIDwtIGN2LmdsbW5ldCh4X3RyYWluLCB5X3RyYWluLCBmb2xkaWQgPSBmb2xkaWQsIGFscGhhID0gYWxwaGEpCmJlc3RfbGFtYmRhIDwtIGN2X21vZGVsJGxhbWJkYS5taW4KbW9kZWxfZW4gPC0gZ2xtbmV0KHhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gYWxwaGEsIGZvbGRpZCA9IGZvbGRpZCwgbGFtYmRhID0gYmVzdF9sYW1iZGEpCgpkb19wbG90IDwtIGZ1bmN0aW9uKGRhdGEsIHR5cGUpIHsKICB4X2V2YWwgPC0gZGF0YSAlPiUgc2VsZWN0KC1uYW1lLC10bWcpICU+JSBhcy5tYXRyaXgoKQogIHlfZXZhbCA8LSBkYXRhJHRtZwogIHhfZXZhbCA8LSBwcmVkaWN0KHNjYWxlZF94LCB4X2V2YWwpCiAgCiAgcHJlZGljdGlvbnMgPC0gcHJlZGljdChtb2RlbF9lbiwgcyA9IGJlc3RfbGFtYmRhLCBuZXd4ID0geF9ldmFsKQogIFJNU0UgPC0gc3FydChtZWFuKChwcmVkaWN0aW9ucyAtIHlfZXZhbCleMikpCiAgcHJpbnQocGFzdGUwKFJNU0UsIHR5cGUpKQogIHJlc3VsdHMgPC0gZGF0YS5mcmFtZShSZWZlcmVuY2UgPSB5X2V2YWwsIFByZWRpY3RlZCA9IGFzLnZlY3RvcihwcmVkaWN0aW9ucykpCiAgcmV0dXJuKGdncGxvdChyZXN1bHRzLCBhZXMoeCA9IFJlZmVyZW5jZSwgeSA9IFByZWRpY3RlZCkpICsKICAgIGdlb21fcG9pbnQoY29sb3I9J2JsdWUnKSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yPSdyZWQnKSArCiAgICBnZ3RpdGxlKHBhc3RlKHR5cGUsICJnbG1uZXQ7IFJNU0U6IiwgUk1TRSkpICsKICAgIHhsYWIoIlJlZmVyZW5jZSBWYWx1ZXMiKSArCiAgICB5bGFiKCJQcmVkaWN0ZWQgVmFsdWVzIikgKwogICAgdGhlbWVfYncoKSkKfQpkb19wbG90KHRlc3RfZGF0YSwgIlRlc3QiKQpkb19wbG90KHRyYWluX2RhdGEsICJUcmFpbiIpCmRvX3Bsb3QoZGF0YXNldCwgIlRvdGFsIikKYmVzdF9sYW1iZGEKYGBgCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJncmlkRXh0cmEiKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShwYXJhbGxlbCkKCnBsb3RfY29sdW1uIDwtIGZ1bmN0aW9uKGNvbHVtbiwgaW1wb3J0YW5jZSl7CiAgcmVzdWx0cyA8LSBkYXRhLmZyYW1lKHggPSBkYXRhc2V0JHRtZywgeSA9IGRhdGFzZXRbW2NvbHVtbl1dKQogIHBsdCA8LSBnZ3Bsb3QocmVzdWx0cywgYWVzKHggPSB4LCB5ID0geSkpICsKICAgIGdlb21fcG9pbnQoY29sb3I9J2JsdWUnKSArCiAgICAgeGxhYigiIikgKwogICAgeWxhYihjb2x1bW4pICsKICAgIHRoZW1lX2J3KCkKICByZXR1cm4ocGx0KSAgCn0KCnBsb3RfaW1wb3J0YW50IDwtIGZ1bmN0aW9uKGltcG9ydGFudCwgdGl0bGUsIG5fdG9fcGxvdD02LCBjb2x1bW5zPTIpIGdyaWQuYXJyYW5nZSh0b3A9dGl0bGUsIGdyb2JzID0gbWNsYXBwbHkoaW1wb3J0YW50JHByZWRpY3RvclsxOm5fdG9fcGxvdF0sIHBsb3RfY29sdW1uLCBpbXBvcnRhbmNlPWltcG9ydGFudCRpbXBvcnRhbmNlWzE6bl90b19wbG90XSksIG5jb2wgPSBjb2x1bW5zKQoKIyBwbG90X2xpc3QxIDwtIGxhcHBseSh0b3AxJHByZWRpY3RvclsxOjZdLCBwbG90X2NvbHVtbikKIyBncmlkLmFycmFuZ2UodG9wPSJBbHBoYT0xIiwgZ3JvYnMgPSBwbG90X2xpc3QxLCBuY29sID0gMikKCiMgcGxvdF9saXN0MCA8LSBsYXBwbHkodG9wMCRwcmVkaWN0b3JbMTo2XSwgcGxvdF9jb2x1bW4pCiMgZ3JpZC5hcnJhbmdlKHRvcD0iQWxwaGE9MCIsIGdyb2JzID0gcGxvdF9saXN0MCwgbmNvbCA9IDIpCnRvcDAKcG5nKCJhbHBoYTAucG5nIiwgd2lkdGggPSAxMDAwLCBoZWlnaHQgPSAxMjAwKQpwbG90X2ltcG9ydGFudCh0b3AwLCBuX3RvX3Bsb3Q9MjAsIGNvbHVtbnM9NCwgIkFscGhhPTAiKQpkZXYub2ZmKCkKcG5nKCJhbHBoYTEucG5nIiwgd2lkdGggPSAxMDAwLCBoZWlnaHQgPSAxMjAwKQpwbG90X2ltcG9ydGFudCh0b3AxLCBuX3RvX3Bsb3Q9MjAsIGNvbHVtbnM9NCwgIkFscGhhPTEiKQpkZXYub2ZmKCkKCmBgYApgYGB7cn0KZnVsbF9kYXRhc2V0IDwtIHJlYWRfY3N2KCJuZXdfZGF0YXNldC5jc3YiKQpmdWxsX2RhdGFzZXQKc2VsZWN0ZWRfY29sdW1ucyA8LSBmdWxsX2RhdGFzZXRbLCBncmVwKCIoXnBzZF8pIiwgbmFtZXMoZnVsbF9kYXRhc2V0KSldCnBsb3QodChzZWxlY3RlZF9jb2x1bW5zWzEsXSkpCmBgYAo=